package com.yunmaozj.tools.lock.annotations;

import com.yunmaozj.tools.lock.interceptor.DistributedLockAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static java.util.Objects.nonNull;

/**
 * @author 钟明宏
 * @version V1.0
 * @date 2021-08-30 13:45
 **/
public class AnnotationDistributedLockAttributeSource {

    private Logger logger = LoggerFactory.getLogger(AnnotationDistributedLockAttributeSource.class);
    private static final DistributedLockAttribute NULL_TRANSACTION_ATTRIBUTE = new DistributedLockAttribute() {
        @Override
        public String toString() {
            return "null";
        }
    };
    private DistributedLockAnnotationParser annotationParser;
    private final Map<Object, DistributedLockAttribute> attributeCache = new ConcurrentHashMap<>(1024);

    public AnnotationDistributedLockAttributeSource(DistributedLockAnnotationParser annotationParser) {
        this.annotationParser = annotationParser;
    }

    public boolean isCandidateClass(Class<?> targetClass) {
        return annotationParser.isCandidateClass(targetClass);
    }

    public DistributedLockAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
        if (method.getDeclaringClass() == Object.class) {
            return null;
        }
        Object cacheKey = getCacheKey(method, targetClass);
        DistributedLockAttribute cached = this.attributeCache.get(cacheKey);
        if (cached != null) {
            if (cached == NULL_TRANSACTION_ATTRIBUTE) {
                return null;
            } else {
                return cached;
            }
        } else {
            DistributedLockAttribute dlAttr = computeTransactionAttribute(method, targetClass);
            if (dlAttr == null) {
                this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
            } else {
                String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
                if (logger.isTraceEnabled()) {
                    logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + dlAttr);
                }
                this.attributeCache.put(cacheKey, dlAttr);
            }
            return dlAttr;
        }
    }

    protected DistributedLockAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
        if (!Modifier.isPublic(method.getModifiers())) {
            return null;
        }

     
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

        DistributedLockAttribute dlAttr = findDistributedLockAttribute(specificMethod);
        if (dlAttr != null) {
            return dlAttr;
        }

        dlAttr = findDistributedLockAttribute(specificMethod.getDeclaringClass());
        if (dlAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return dlAttr;
        }

        if (specificMethod != method) {
            dlAttr = findDistributedLockAttribute(method);
            if (dlAttr != null) {
                return dlAttr;
            }
            dlAttr = findDistributedLockAttribute(method.getDeclaringClass());
            if (dlAttr != null && ClassUtils.isUserLevelMethod(method)) {
                return dlAttr;
            }
        }

        return null;
    }

    protected DistributedLockAttribute findDistributedLockAttribute(Class<?> clazz) {
        return determineDistributedLockAttribute(clazz);
    }

    protected DistributedLockAttribute determineDistributedLockAttribute(Class<?> clazz) {
        return annotationParser.parseTransactionAnnotation(clazz);
    }

    protected DistributedLockAttribute findDistributedLockAttribute(Method method) {
        return determineDistributedLockAttribute(method);
    }

    protected DistributedLockAttribute determineDistributedLockAttribute(Method method) {
        return annotationParser.parseTransactionAnnotation(method);
    }

    protected Object getCacheKey(Method method, @Nullable Class<?> targetClass) {
        return new MethodClassKey(method, targetClass);
    }
}
